let { isObject, isFunction } = require("./lib/helper");
let Metadata = require("./lib/metadata");
let { DataSet, Service } = require("./dataset");
let { setSourcePaths } = require("./ctx/util");

function checkDataSetMetaInfo(type) {
	if (isFunction(type)) {
		type = { service: type };
	}
	let checked = true, errorInfo = "", _info = Object.assign({
		type: DataSet,
		service: null
	}, type);
	if (!(isFunction(_info.type) && (_info.type.prototype instanceof DataSet) || _info.type === DataSet)) {
		checked = false;
		errorInfo = "[ada] dataset must be a class or subclass of  DataSet";
	}
	if (!(isFunction(_info.service) && (_info.service.prototype instanceof Service || _info.service === Service))) {
		checked = false;
		errorInfo = "[ada] service must be a class or subclass of  Service";
	}
	return { checked, errorInfo, info: _info };
}

function setViewAnnotation(tag, info) {
	if (info === null || isObject(info)) {
		return function (target) {
			let _info = info || {};
			if (_info.dataset) {
				let { checked, errorInfo, info } = checkDataSetMetaInfo(_info.dataset);
				if (checked) {
					_info.dataset = info;
				} else {
					throw Error(`[data] view dataset error ${errorInfo}`);
				}
			}
			setSourcePaths(_info);
			Metadata.defineMetadata(tag, _info, target.prototype);
		}
	} else {
		Metadata.defineMetadata(tag, {}, info.prototype);
	}
}

function setMethodAnnotation(tag, names) {
	if (names.length > 0) {
		return function (target, methodName) {
			let info = { ...Metadata.getMetadata(tag, target) || {} };
			(names || []).forEach(key => info[key] = methodName);
			Metadata.defineMetadata(tag, info, target);
		}
	} else {
		throw Error(`[ada] annotation ${tag} can not be empty`);
	}
}

exports.view = function (info = null) {
	return setViewAnnotation("view", info);
};

exports.action = function (...names) {
	if (names.find(a => a === 'self' || a === '@')) {
		throw Error('action name can not be "self" or "@"');
	} else {
		return setMethodAnnotation("action", names);
	}
};

exports.frames = function (...names) {
	if (names.length > 0) {
		return function (target, methodName) {
			let info = { ...Metadata.getMetadata('frames', target) || {} };
			info[methodName] = names.map(a => a.trim());
			Metadata.defineMetadata('frames', info, target);
		}
	} else {
		throw Error(`[ada] annotation frames can not be empty`);
	}
};

exports.compute = function (...names) {
	return setMethodAnnotation("compute", names);
};

exports.watch = function (...names) {
	return setMethodAnnotation("watch", names);
};

exports.inspect = function (...names) {
	return setMethodAnnotation("inspect", names);
};

exports.source = function (info) {
	if (info) {
		return function (target, methodName) {
			let _info = { ...Metadata.getMetadata('source', target) || {} };
			_info[methodName] = info;
			Metadata.defineMetadata('source', _info, target);
		}
	} else {
		throw Error(`[ada] annotation source info can not be empty`);
	}
};

exports.handler = function (...names) {
	return setMethodAnnotation("handler", names);
};

exports.binder = function (...names) {
	return setMethodAnnotation("binder", names);
};

exports.subscribe = function (...names) {
	return setMethodAnnotation("subscribe", names);
};